Vala 是由 GNOME 小矮人开发的面向对象编程语言。编程语法接近 Java,围绕 GLib 库展开。编译方式是先翻译成 C 语言代码,然后编译。用途嘛……我来讲个故事吧。
我半年前学了 Dart,Google 开发的语言,编程语法接近 Javascript。官网说它是“多用途语言”,然而我感觉多数人学了它,就是为了用 Flutter :-P
Vala 也是这样,名义上是一个“多用途语言”,但是我感觉多数人学了它,只是为了 GTK。我也是不知道为啥,非得用这个语言写我的数据库大作业,花了两周时间边学边写,最后也不知道我学了个啥……
不得不说,GLib 是一个很强大的库。本来说是给 GTK 服务的,后来独立出去了。它实现了单/双向链表,变长数组,树,Map 等数据结构。它还以 GObject
为中心,构建了一个相当完善的,庞大的,让我这个菜鸡不知所以的类系统。
接下来大致介绍顺序:
基本输入输出(从键盘输入,从终端输出)
判断语句 if-else 和 switch
循环语句,包括计数和计事件循环
我一点都不懂的面向对象
GLib 库和 Gee 库
SQLite 3 库
先给大家推荐一些前人的经验教训:
官方演示:Projects/Vala/BasicSample - GNOME Wiki!
输出一句话,都是那德行:
void main() {
// GLib 的 print 函数
print("Clapton is GOD!");
// 使用到了 stdin / stdout / stderr 对象
stdout.printf("%s is GOD!","Clapton");
}
输入一个数字:
void main() {
// 双精度浮点数
double a;
// 类似 C 语言的 scanf,注意 out
// 不是取地址符
stdin.scanf("%lf",out a);
// 类似 C 语言的 printf
stdout.printf("%.3f",a);
int b;
stdin.scanf("%d",out b);
stdout.printf("%d",b);
}
输入字符串:
void main() {
stdout.printf("Welcome, what's your name?");
string a = stdin.read_line();
stdout.printf("OK, %s, main course is prepared for you.",a);
}
if-else 判断:
void main() {
stdout.printf("Enter a year: ");
int year;
stdin.scanf("%d",out year);
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0 ) {
print("Maybe Olympics if no war.");
} else {
print("No Olympics.");
}
}
swtich 判断:
省略,很少用到。
计次数循环 for
// 金字塔输出
void main() {
int a;
stdin.scanf("%d",out a);
for (int i = 1; i <= a; i += 2){
for (int j = 0; j < (a - i) / 2; ++j) {
print(" ");
}
for (int j = 0; j < i; ++j) {
print("*");
}
print("\n");
}
}
计事件循环 while
// Vala 引用库的方式
using Random;
void main() {
// 这里我用了随机数类
int toGuess = Random.int_range (0, 50);
while (true) {
int a;
stdout.printf("Enter a number: ");
stdin.scanf("%d", out a);
if (a == toGuess) {
break;
}
if (a < toGuess) {
print("Think larger.\n");
} else {
print("Think smaller.\n");
}
}
print("Match\n");
}
先说一句,我面向对象课学的一塌糊涂,如果想了解更多,请看官方介绍:Project/Vala/Tutorial#OOP
注意,Vala 基于 GLib,GLib 包含 GObject,GObject 仅支持单向继承。所以,跟 Java 一样,Vala 是单继承+接口。
从大家喜闻乐见的开始:
class Animal {
// 类里面的变量
// 和 Java 一样,有 private protected public
protected string name;
// 构造函数
public Animal (string _name){
name = _name;
}
// 析构函数,一般不用写
// ~Animal();
// 方法
public void action () {
print("Punish you in the name of the moon, ");
}
}
class Cat : Animal{
private bool cute;
public Cat (string _name, bool cute) {
// base() 调取父类构造函数,必须写
base(_name);
this.cute = cute;
}
// 重写方法需要加 "new"
public new void action () {
base.action ();
print(cute ? "meow~" : "graw~");
}
}
void main() {
Cat a = new Cat("A",true);
a.action();
}
这个是我从网上抄的一段代码:
// 接口,也就是不能被实例化的虚类。
interface Printable {
// abstract 要由继承的类实现
public abstract string print ();
// virtual 有默认的实现
public virtual string pretty_print () {
return "Please " + print ();
}
}
class NormalPrint: Object, Printable {
// 实现上面的 abstract
string print () {
return "don't forget about me\n";
}
}
class OverridePrint: Object, Printable {
string print () {
return "Mind the gap\n";
}
// 重载函数,覆盖 virtual 的默认实现
public override string pretty_print () {
return "Override\n";
}
}
void main (string[] args) {
var normal = new NormalPrint ();
var overridden = new OverridePrint ();
print (normal.pretty_print ());
print (overridden.pretty_print ());
}
Gee 相当于 C++ 里面的 STL 。我对这个了解不多,先把官方的示例贴上来:Projects/Vala/GeeSamples - GNOME Wiki!
实际上 GLib 已经实现了很多的数据结构,但我个人建议 Gee,功能比 Glib 本身有的更丰富,但是编程的时候需要添加 Gee 库。
using Gee;
Glib 中,我有用过:
Array<类型>:变长数组
List<类型>:双向列表
Gee中,我有用过:
Set<类型>:无重复集合
HashMap<类型1,类型2>:哈希字典
具体用法请参阅相关文档和示例,链接给完了,我溜了~
首先是匿名函数,很简单:
(函数形参)=>{函数体语句}
(函数形参)=>一条语句
一般用于函数作形参的时候,临时写一个简单的。比如下面那个情况。
还有迭代,有些预先定义好的数据结构都支持迭代,使用的时候使用 foreach
方法就好。比如说:
void main () {
List<int> a = new List<int>();
a.append (1);
a.append (2);
a.append (3);
a.append (4);
a.append (5);
// foreach 方法需要一个函数,这里面的就是匿名函数
a.foreach((i)=>print(i.to_string ()));
}
先写出一个错误空间,说明这是啥大类的错误,里面可以细分。
public errordomain DatabaseError {
COULDNT_OPEN,
EXECUTION_FAILED,
PREPARATION_FAILED,
BIND_FAILED,
INVALID_GAME,
NOT_FOUND,
}
写函数/方法的时候,可以加入 throws
关键字,注明会抛出啥错误。里面需要抛出错误的时候,使用 throw
语句抛出。下面是一个例子:
// 说明这个函数会抛出 DatabaseError 错误
private void open () throws DatabaseError {
int sql_return = Sqlite.Database.open_v2 (NAME_OF_DB, out m_db);
if (sql_return != Sqlite.OK) {
// 这句话,先新建一个错误类,里面写的是具体内容,然后抛出
throw new DatabaseError.COULDNT_OPEN ("Cannot create database: %d, %s\n", sql_return, m_db.errmsg ());
}
}
要捕捉抛出的错误,请使用 try-catch-finally 语句:
public void createDatabase () {
try {
open ();
exec (CREATE_FLIGHT_TABLE_QUERY);
exec (CREATE_HOTEL_TABLE_QUERY);
exec (CREATE_BUS_TABLE_QUERY);
exec (CREATE_CUSTOMER_TABLE_QUERY);
exec (CREATE_RESERVATION_TABLE_QUERY);
// 错误被捕捉到了
} catch (DatabaseError e) {
stderr.printf ("%s\n", e.message);
}
// 可以加写一个 finally,finally 总会被运行
}
Vala 的变量可以设为空值,方法是加一个问号:
void main () {
// 这句话会报错
int a = null;
// 这句话不会报错
int ? b = null;
}
我个人认为,如果你不能确保方法确实能返回一个元素,可以使用这个。
int ? a () {
try {
int result;
// 我没在摸鱼
return result;
} catch (CatchFishBeFoundError e) {
return null;
}
}
当然,可以不用这么麻烦,这只是一个例子。
SQLite 是一个库,实现了很完备的关系数据库。它将数据库存在一个文件里,使用的时候,调用 SQLite 库相应的函数,来对这个文件数据库进行基本操作。
这东西是一个 C 语言库。但 Vala 可以使用 C 库,它使用 vapi 文件来对应 C 的头文件。(实际上 Vala 也可以写 C 语言库,毕竟这玩意最后还是会变成 C 语言来编译。)
所以说,Vala 的 SQLite 库用起来应该和 C 语言的差不多。不过请注意,Vala 是面向对象的,而 SQLite 的库在引用到 Vala 的时候,做了面向对象的处理。
使用前,引用这个库:
using Sqlite;
如此定义一个数据库对象:
Sqlite.Database m_db;
打开数据库:
Sqlite.Database.open_v2 (string path, out Sqlite.Database);
执行语句:
m_db.exec (string sql_exec);
定义方式如下:
Sqlite.Statement add_flight;
准备声明:
public Sqlite.Statement prepare (string sql) throws DatabaseError {
Sqlite.Statement statement;
// 加不加 v2 都行,需要 sql 语句字符串,字符串长度,输出到一个 statement 类
int sql_result = m_db.prepare_v2 (sql, sql.length, out statement);
if (sql_result != Sqlite.OK) {
throw new DatabaseError.PREPARATION_FAILED ("Cannot prepare satement for %s: %d, %s\n", sql, sql_result, m_db.errmsg ());
}
return statement;
}
绑定声明:
绑定依然有一系列的函数,此处只看绑定字符串
private void bind_text (Sqlite.Statement statement, string stmt, string text) throws DatabaseError {
// 这是寻找 statement 中 stmt 的位置
int index = statement.bind_parameter_index (stmt);
if (index <= 0) {
throw new DatabaseError.BIND_FAILED ("Could not bind %s: %s not found in the statement %s.\n", text, stmt, statement.sql ());
}
// 绑定,index 是索引,text 是要绑定的字符串
int sql_result = statement.bind_text (index, text);
if (sql_result != Sqlite.OK) {
statement.reset ();
throw new DatabaseError.BIND_FAILED ("Could not bind %s: %d, %s\n", text, sql_result, m_db.errmsg ());
}
}
执行声明并清除绑定:
private void step (Sqlite.Statement statement) throws DatabaseError {
// 执行声明
int sql_return = statement.step ();
// 清除绑定
statement.reset ();
if (sql_return != Sqlite.DONE) {
throw new DatabaseError.EXECUTION_FAILED ("Execute failed: %d, %s\n", sql_return, m_db.errmsg ());
}
return;
}
循环取出返回值:
// 摘抄自我的大作业代码
public HashMap<string, HashSet<string>> ? avaliable () {
try {
var Graph = new HashMap<string, HashSet<string>> ();
// 创建一个声明,这个是一个查询语句
Sqlite.Statement get_flight = this.prepare ("SELECT FromCity,ArivCity FROM FLIGHT;");
// 我前面说过返回值的事情,Sqlite.ROW
while (get_flight.step () == Sqlite.ROW) {
string from = get_flight.column_text (0);
string to = get_flight.column_text (1);
if (!Graph.has_key (from)) {
Graph[from] = new HashSet<string> ();
}
Graph[from].add (to);
}
return Graph;
} catch (DatabaseError e) {
stdout.printf (e.message);
return null;
}
}
我当时是这么学的 C 语言:
基本输入输出
判断语句
循环语句
函数
数组
结构体
指针
前三条是说明这个语言大致的语法如何,因为编程思维的逻辑无非就那些:从哪里开始,需要那些材料,需要经过那些步骤,那些步骤得不断进行,这个步骤执行的条件是什么,这个步骤的结束条件是什么,最后的成果是如何的?逻辑搞明白了,接下来就是靠语言实现了。
接下来第四条,我认为是说明这个语言的性质。C 语言是面向过程的语言,所以主要是函数。而要是面向对象的话,教完函数之后,就是教你如何写一个类,如何搞继承之类的了。
剩下那三个,说明这个语言的数据结构。数据结构,有链表,栈,队列,字符串,树,图之类,还有集合,键值对字典这些常用的。这些东西给你了实现的工具,不过大多数语言已经实现了,比如 Java 。
最后,速通了语言,不代表所有。你得找到相对应的库。要是库很缺乏,或者根本没学的话,很有可能你啥都干不了。我暑假两天速通了 Javascript,然后我由于没学任何 Javascript 的库,比如 vue / react 啥的,我都不知道要用这个来干嘛:-P
最后,如有不完备或错误之处,敬请谅解。我还是水平不够啊:-(
SuperBart 2022-12-20